//--------------------------------------------------------------------------------------------------
//                                                   ___  __  __  __  __  __  __  __    __  __  ___ 
// Copyright 2010, Koert van der Veer.              / __)(  )(  )(  \/  )(  )(  )(  )  (  )(  )/ __)
// Licensed under the Apache License,              ( (__  )(__)(  )    (  )(__)(  )(__  )(__)( \__ \
// version 2.0; ("the License")                     \___)(______)(_/\/\_)(______)(____)(______)(___/

// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
//     http://www.apache.org/licenses/LICENSE-2.0 
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS,  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under
// the License. 
//--------------------------------------------------------------------------------------------------
// This file is part of cumulus,  an memory manager implementation in C++.  Details are discussed in
// my blog,  which can be found at http://ondergetekende.nl.  Comments,  suggestions and bug reports
// are welcome. They can be added as comments at the blog. Be advised that in its current form, this
// code is most likely not production ready; it simply hasn't been tested sufficiently. 
//--------------------------------------------------------------------------------------------------

#pragma once

#include "Misc.h"

namespace Cumulus
{

// Enforces minimum alignment by prepending a variable-sized header before the allocation
template< size_t AlignParam, typename InnerLayer >
class EnforceMinAlignment : public InnerLayer
{
	// Static assertions
	static CompileTimeAssert< (AlignParam & (AlignParam-1)) == 0 > AlignParamShouldBePowerOfTwo;

public:
	static const size_t MinAlignment = AlignParam;
	static const size_t MinSize = InnerLayer::MinSize < MinAlignment ?
		1 :
		InnerLayer::MinSize;

public:
	/// Returns a pointer to the a new piece of memory, of at least Size.
	/// After the call, Size may be changed to a larger number. In that case,
	/// the caller may use up Size bytes. Note that successive calls to GetSize
	/// may return a different (larger) number.
	/// @param  Size	in/out The requested size of the new memory allocation. Must be > 0. 
	///					Post: the size usable for writes, larger than
	/// @return			A pointer to the new memory block, or NULL if the allocation was
	/// 				not successful.
	void* Malloc( size_t& Size );

	/// Returns ownership of a block of memory to the memory manager.        
	/// @param  Memory	Pointer to the allocation be released. Must be a valid pointer, as 
	//					previously returned by Malloc. (NULL is invalid)
	void Free( void* Memory );

	/// Returns the client usable size for the provided pointer
	/// This is guaranteed to be equal or larger than the request size provided to Malloc.
	/// Successive calls to GetSize will return the same value, unless Resize was called earlier.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc. Any other pointer (including NULL) is invalid.
	size_t GetSize( void* Memory );

	/// Tries to change the size of the allocated block to at least NewSize.
	/// After the call, NewSize may be changed to a larger number. In that case,
	/// the caller may use up Size bytes. Note that successive calls to GetSize
	/// may return a different (larger) number.
	/// @param Memory	Pointer to allocation to be modified. Must be a valid pointer, as previously
	///					returned by Malloc. Any other pointer (including NULL) is invalid.
	/// @param NewSize	in/out The new size for the allocation
	/// @return			TRUE if the resize operation was successful.
	bool Resize( void* Memory, size_t& NewSize );

	/// Enumerator for memory allocations.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc, or NULL to request the first Allocation.
	/// @return			A pointer to the memory allocation after Memory.
	void* GetNext( void* Memory );

	/// Reverse enumerator for memory allocations.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc, or NULL to request the last Allocation.
	/// @return			A pointer to the memory allocation after Memory.
	void* GetPrevious( void* Memory );


private:

	// A header to store the amount of padding used. The amount of padding includes the size of this
	// structure.
	struct PaddingInfo
	{
		unsigned short Padding;
	};

	// Conversion function to convert pointer from the client to the inner layer, and vice versa.
	void* ClientToInner( void* Memory );
	void* InnerToClient( void* Memory );
};

// Makes sure the provided Alignment is guaranteed. If InnerLayer already provides
// sufficient guarantees, no extra code will be added. (Except for a compile time assertion)
template< size_t AlignParam, typename InnerLayer >
class RequireMinAlignment : public SelectType< 
	InnerLayer::MinAlignment >= AlignParam, 
	InnerLayer,
	EnforceMinAlignment<AlignParam, InnerLayer> >::Result
{
	static CompileTimeAssert< (AlignParam & (AlignParam-1)) == 0 > AlignParamShouldBePowerOfTwo;
};


template< size_t AlignParam, typename InnerLayer >
void* EnforceMinAlignment<AlignParam,InnerLayer>::Malloc( size_t& Size ) 
{
	// Add worst case padding and allocate
	Size += MinAlignment + sizeof(PaddingInfo);
	void* InnerMemory = InnerLayer::Malloc( Size );

	// Calculate the actual padding
	size_t MemoryAddr = (size_t)InnerMemory;
	size_t Padding = MinAlignment - (MemoryAddr & (MinAlignment-1));

	// Make sure there is enough room for the header.
	if( Padding < sizeof(PaddingInfo) )
	{
		Padding += MinAlignment;
	}

	// Adjust the result pointer
	void* Result = ((char*) InnerMemory) + Padding;

	// Store the pointer in the padding info
	PaddingInfo* Header = GetHeader<PaddingInfo>(Result);
	Header->Padding = (unsigned short)Padding;

	// make sure we can get back to the inner memory pointer
	assert( ClientToInner(Result) == InnerMemory );

	// Adjust size for Padding. Note that initially we added the worst case scenario, so size will 
	// still be greater or equal to the initial request.
	Size -= Padding;

	return Result;
}

// Returns ownership of a block of memory to the memory manager.
template< size_t AlignParam, typename InnerLayer >
void EnforceMinAlignment<AlignParam,InnerLayer>::Free( void* Memory ) 
{
	InnerLayer::Free( ClientToInner(Memory) );
}

// Returns the client usable size for the provided pointer
template< size_t AlignParam, typename InnerLayer >
size_t EnforceMinAlignment<AlignParam,InnerLayer>::GetSize( void* Memory )
{
	PaddingInfo* Header = GetHeader<PaddingInfo>(Memory);

	size_t Result = InnerLayer::GetSize( ClientToInner(Memory) );

	return Result - Header->Padding;
}

template< size_t AlignParam, typename InnerLayer >
bool EnforceMinAlignment<AlignParam,InnerLayer>::Resize( void* Memory, size_t& NewSize )
{
	PaddingInfo* Header = GetHeader<PaddingInfo>(Memory);

	NewSize += Header->Padding;

	bool ResizeSuccessful = InnerLayer::Resize( ClientToInner(Memory), NewSize );

	NewSize -= Header->Padding;

	return ResizeSuccessful;
}

template< size_t AlignParam, typename InnerLayer >
void* EnforceMinAlignment<AlignParam,InnerLayer>::GetNext( void* Memory ) 
{ 
	void* UnalignedResult = InnerLayer::GetNext( ClientToInner( Memory ) );

	if( UnalignedResult == NULL )
	{
		return NULL;
	}
	else
	{
		return InnerToClient( NextHeader );
	}
}

template< size_t AlignParam, typename InnerLayer >
void* EnforceMinAlignment<AlignParam,InnerLayer>::GetPrevious( void* Memory ) 
{ 
	void* UnalignedResult = InnerLayer::GetPrevious( ClientToInner( Memory ) );

	if( UnalignedResult == NULL )
	{
		return NULL;
	}
	else
	{
		return InnerToClient( NextHeader );
	}
}



// Converts a client pointer to a pointer for the inner malloc
template< size_t AlignParam, typename InnerLayer >
void* EnforceMinAlignment<AlignParam,InnerLayer>::ClientToInner( void* Memory )
{
	PaddingInfo* Header = (PaddingInfo*) Memory - 1;
	return (char*)Memory - Header->Padding;
}

template< size_t AlignParam, typename InnerLayer >
void* EnforceMinAlignment<AlignParam,InnerLayer>::InnerToClient( void* Memory )
{
	// Calculate the actual padding
	size_t MemoryAddr = (size_t)InnerMemory;
	size_t Padding = MinAlignment - (MemoryAddr & (MinAlignment-1));

	// Adjust the result pointer
	return ((char*) InnerMemory) + Padding;
}
};